package game;

import game.console.Console;
import game.gol.GameOfLife;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.io.PrintWriter;
import java.io.StringWriter;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class Game implements Runnable
{
    private static Game instance;
    
    public synchronized static Game getInstance()
    {
        if (instance == null)
        {
            instance = new Game();
            instance.initGOL();
        }
        return instance;
    }
    
    public static synchronized void log(Object message, boolean newLine)
    {
        if (message instanceof Throwable)
        {
            System.out.println(((Throwable) message).getLocalizedMessage());
            ((Throwable) message).printStackTrace(System.out);
        }
        else
        {
            System.out.print(message.toString());
        }
        if (newLine)
        {
            System.out.println();
        }
    }
    
    public static synchronized void log(Object message)
    {
        log(message, true);
    }
    
    public final int WINDOW_WIDTH  = 1500;
    public final int WINDOW_HEIGHT = 800;

    private JFrame   frame;
    private JPanel   panel;
    private int      width;
    private int      height;
    
    private boolean  fullscreen    = false;
    
    private Game()
    {
        // panel
        panel = new JPanel()
        {
            private static final long serialVersionUID = -1923410016305041430L;

            @Override
            protected void paintComponent(Graphics g)
            {
                render(g);
            }
        };
        panel.setLayout(null);
        panel.setIgnoreRepaint(true);
        // frame
        frame = new JFrame("Basic Game");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        Rectangle size =  GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration().getBounds();
        if (fullscreen)
        {
            frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
            setSize(size.width, size.height);
        }
        else
        {
            setSize(size.width / 2, size.height / 2);
        }
        frame.setContentPane(panel);
        frame.pack();
        frame.setVisible(true);
//        frame.setResizable(false);
        frame.addComponentListener(new ComponentControl());
        // listener
        panel.requestFocus();
    }
    
    private final GameOfLife GOL_INSTANCE = GameOfLife.getInstance();
    
    private void initGOL()
    {
        panel.addKeyListener(GOL_INSTANCE.getKeyboardControl());
        panel.addMouseListener(GOL_INSTANCE.getMouseControl());
        panel.addMouseMotionListener(GOL_INSTANCE.getMouseMotionListener());
    }
    
    public int getWidth()
    {
        return width;
    }
    
    public int getHeight()
    {
        return height;
    }
    
    private void setSize(int width, int height)
    {
        panel.setPreferredSize(new Dimension(width, height));
        panel.setBounds(0, 0, width, height);
        this.width = width;
        this.height = height;
    }
    
    public void setTitle(String title)
    {
        frame.setTitle(title);
    }
    
    private class ComponentControl extends ComponentAdapter
    {
        private boolean   isResizing;
        private long      resizeTime   = 0;
        private final int DELAY        = 600;
        
        private Thread resizeThread = new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                while (GAME_RUNNING)
                {
                    try
                    {
                        if (isResizing)
                        {
                            if (GOL_INSTANCE.isRunning())
                            {
                                GOL_INSTANCE.requestStop();
                            }
                            long delay = System.currentTimeMillis() - resizeTime;
                            if (delay > DELAY)
                            {
                                setSize(newSize.width, newSize.height);
                                GOL_INSTANCE.requestRestart(calcSize());
                                isResizing = false;
                            }
                            else
                            {
                                Thread.sleep(delay);
                            }
                        }
                        Thread.sleep(DELAY);
                    }
                    catch (InterruptedException e)
                    {
//                        e.printStackTrace(System.err);
                    }
                }
            }
        });
        
        private Dimension newSize;
        
        public ComponentControl()
        {
            resizeThread.start();
        }
        
        @Override
        public void componentShown(ComponentEvent e)
        {
        }
        
        @Override
        public void componentResized(ComponentEvent e)
        {
            newSize = e.getComponent().getSize();
            resizeTime = System.currentTimeMillis();
            isResizing = true;
        }
        
        @Override
        public void componentMoved(ComponentEvent e)
        {
        }
        
        @Override
        public void componentHidden(ComponentEvent e)
        {
        }
    }
    
    private int       desiredFPS = 20;
    
    public int getDesiredFPS()
    {
        return desiredFPS;
    }
    
    public void setDesiredFPS(int desiredFPS)
    {
        this.desiredFPS = desiredFPS;
    }
    
    public int recalcDesiredDeltaLoop()
    {
        return (1000 * 1000 * 1000) / desiredFPS;
    }
    
    private boolean GAME_RUNNING = true;
    
    public void run()
    {
        GOL_INSTANCE.requestRestart(calcSize());
        while (GAME_RUNNING)
        {
            try
            {
                long beginLoopTime = System.nanoTime();
                SwingUtilities.invokeAndWait(new Runnable()
                {
                    
                    @Override
                    public void run()
                    {
                        panel.repaint();
                    }
                });
                update();
                long endLoopTime = System.nanoTime();
                long deltaLoop = endLoopTime - beginLoopTime;
                long desiredDeltaLoop = recalcDesiredDeltaLoop();
                if (deltaLoop <= desiredDeltaLoop )
                {
                    try
                    {
                        Thread.sleep((desiredDeltaLoop - deltaLoop) / (1000 * 1000));
                    }
                    catch (InterruptedException e)
                    {
                        // Do nothing
                    }
                }
            }
            catch (Exception ex)
            {
                StringWriter sw = new StringWriter();
                ex.printStackTrace(new PrintWriter(sw));
                log(sw.toString());
                GAME_RUNNING = false;
            }
        }
    }
    
    private short cellSize = 15;
    
    public short getCellSize()
    {
        return cellSize;
    }
    
    public void setCellSize(short cellSize)
    {
        this.cellSize = cellSize;
    }
    
    public Point calcSize()
    {
        int width = this.width;
        if (this.width % cellSize != 0)
        {
            width -= this.width % cellSize;
        }
        width /= cellSize;
        int height = this.height;
        if (this.height % cellSize != 0)
        {
            height -= this.height % cellSize;
        }
        height /= cellSize;
        return new Point(width, height);
    }
    
    public Point calcCoord(java.awt.Point p)
    {
        if (p.getX() > width)
        {
            throw new IllegalArgumentException(p.getX() + " > " + width);
        }
        if (p.getY() > height)
        {
            throw new IllegalArgumentException(p.getY() + " > " + height);
        }
        int x = (int) (p.getX() / cellSize);
        int y = (int) (p.getY() / cellSize);
        return new Point(x, y);
    }
    
    /**
     * Rewrite this method for your game
     */
    private void update()
    {
        GOL_INSTANCE.update();
        // title
        StringBuilder title = new StringBuilder(String.valueOf(desiredFPS)).append(" FPS; ");
        title.append(GOL_INSTANCE.getStatus());
        setTitle(title.toString());
    }
    
    /**
     * Rewrite this method for your game
     */
    private void render(Graphics g)
    {
        GOL_INSTANCE.render(g, cellSize);
        Console con = Console.getInstance();
        if (con.isOpen())
        {
            con.render(g, cellSize);
        }
    }
    
    private void clearScreen(Graphics2D g, int width, int height)
    {
        if (width > this.width)
        {
            width = this.width;
        }
        if (height > this.height)
        {
            height = this.height;
        }
        Color color = g.getColor();
        g.setColor(Color.WHITE);
        g.fillRect(0, 0, width, height);
        g.setColor(color);
    }
    
    private void renderCenterText(Graphics g, String s, int fontSize)
    {
        g.setColor(Color.BLACK);
        g.setFont(new Font("Monospaced", Font.PLAIN, fontSize));
        int w2 = g.getFontMetrics().stringWidth(s) / 2;
        int h2 = g.getFontMetrics().getDescent();
        g.drawString(s, width / 2 - w2, height / 2 + h2);
    }
    
    public static void main(String[] args)
    {
         Thread t = new Thread(Game.getInstance(), "Game");
         t.start();
    }
    
}
